package com.baidu.bas.db.dao;

import cn.hutool.core.lang.Assert;
import com.baidu.bas.annotation.sub_erupt.Filter;
import com.baidu.bas.core.query.Condition;
import com.baidu.bas.core.query.EruptQuery;
import com.baidu.bas.core.service.EruptCoreService;
import com.baidu.bas.core.util.AnnotationUtil;
import com.baidu.bas.core.util.ReflectUtil;
import com.baidu.bas.core.view.EruptFieldModel;
import com.baidu.bas.core.view.EruptModel;
import com.baidu.bas.core.view.Page;
import com.baidu.bas.db.model.BaseModel;
import com.baidu.bas.db.service.EntityManagerService;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Query;
import java.util.*;
import java.util.stream.Collectors;

import static com.baidu.bas.core.util.EruptUtil.convertObjectType;
import static com.baidu.bas.db.dao.EruptJpaUtils.COMPLEX_FILED_JOINER;

/**
 * @author liyuepeng
 * @date 2018-10-11.
 */
@Repository
public class EruptJpaDao {

    @Resource
    private EntityManagerService entityManagerService;

    public <T extends BaseModel> T update(Class<?> eruptClass, T entity) {
        Assert.isTrue(!entity.isNew(), "Entity is new");
        entityManagerService.getEntityManager(eruptClass, (em) -> {
            val target = findById(eruptClass,entity.getId());
            //只更新非空的字段
            String[] nullProperties = ReflectUtil.getNullProperties(entity);
            BeanUtils.copyProperties(entity, target, nullProperties);
            em.merge(target);
            em.flush();
        });
        return entity;
    }

    public <T>  T findById(Class<T> eruptClass, Object id) {
        EntityManager entityManager = entityManagerService.getEntityManager(eruptClass);
        return entityManager.find(eruptClass, id);
    }

    public void addEntity(Class<?> eruptClass, Object entity) {
        entityManagerService.getEntityManager(eruptClass, (em) -> {
            em.persist(entity);
            em.flush();
        });
    }

    public void editEntity(Class<?> eruptClass, Object entity) {
        entityManagerService.getEntityManager(eruptClass, (em) -> {
            em.merge(entity);
            em.flush();
        });
    }

    public void removeEntity(Class<?> eruptClass, Object entity) {
        entityManagerService.getEntityManager(eruptClass, (em) -> {
            em.remove(entity);
            em.flush();
        });
    }

    /**
     * 查询列表
     * TODO 这块代码复制太多,需要修改.
     * @param eruptModel
     * @param eruptQuery
     * @return
     */
    public List queryEruptList(EruptModel eruptModel, EruptQuery eruptQuery){
        String hql = EruptJpaUtils.generateEruptJpaHql(eruptModel,"",eruptQuery,false);
        EntityManager entityManager = entityManagerService.getEntityManager(eruptModel.getClazz());
        Query query = entityManager.createQuery(hql);
        Map<String, EruptFieldModel> eruptFieldMap = eruptModel.getEruptFieldMap();
        if (null != eruptQuery.getConditions()) {
            for (Condition condition : eruptQuery.getConditions()) {
                EruptFieldModel eruptFieldModel = eruptFieldMap.get(condition.getKey());
                switch (condition.getOperator()) {
                    case eq:
                    case EQ:
                        Object value = convertObjectType(eruptFieldModel, condition.getValue());
                        query.setParameter(condition.getKey(), value);
                        break;
                    case like:
                    case LIKE:
                        String likeValue = EruptJpaUtils.PERCENT + condition.getValue() + EruptJpaUtils.PERCENT;
                        query.setParameter(condition.getKey(), likeValue);
                        break;
                    case range:
                    case RANGE:
                        List<?> list = (List<?>) condition.getValue();
                        Object begin = convertObjectType(eruptFieldModel, list.get(0));
                        Object end = convertObjectType(eruptFieldModel, list.get(1));

                        query.setParameter(EruptJpaUtils.LVAL_KEY + condition.getKey(), begin);
                        query.setParameter(EruptJpaUtils.RVAL_KEY + condition.getKey(), end);
                        break;
                    case in:
                    case IN:
                        List<?> listIn = (List<?>) condition.getValue();
                        val params = (List<Object>)condition.getValue();
                        List<Object> listIn2 =  params.stream().map(it->convertObjectType(eruptFieldModel, it))
                                .collect(Collectors.toList());
                        query.setParameter(condition.getKey(), listIn2);
                        break;
                    default:
                        break;
                }
            }
        }
        return query.getResultList();
    }

    public Page queryEruptList(EruptModel eruptModel, Page page, EruptQuery eruptQuery) {
        String hql = EruptJpaUtils.generateEruptJpaHql(eruptModel, "new map(" + String.join(",",
                EruptJpaUtils.getEruptColJapKeys(eruptModel,eruptQuery.getLazyFetchFields())) + ")", eruptQuery, false);
        String countHql = EruptJpaUtils.generateEruptJpaHql(eruptModel, "count(*)", eruptQuery, true);
        EntityManager entityManager = entityManagerService.getEntityManager(eruptModel.getClazz());
        Query query = entityManager.createQuery(hql);
        Query countQuery = entityManager.createQuery(countHql);
        Map<String, EruptFieldModel> eruptFieldMap = eruptModel.getEruptFieldMap();
        if (null != eruptQuery.getConditions()) {
            for (Condition condition : eruptQuery.getConditions()) {
                final EruptFieldModel eruptFieldModel = findEruptFieldModel(eruptFieldMap, condition);
                switch (condition.getOperator()) {
                    case eq:
                    case EQ:
                        Object value = convertObjectType(eruptFieldModel, condition.getValue());
                        countQuery.setParameter(condition.getParamKey(), value);
                        query.setParameter(condition.getParamKey(), value);
                        break;
                    case like:
                    case LIKE:
                        String likeValue = EruptJpaUtils.PERCENT + condition.getValue() + EruptJpaUtils.PERCENT;
                        countQuery.setParameter(condition.getParamKey(), likeValue);
                        query.setParameter(condition.getParamKey(), likeValue);
                        break;
                    case range:
                    case RANGE:
                        List<?> list = (List<?>) condition.getValue();
                        Object begin = convertObjectType(eruptFieldModel, list.get(0));
                        Object end = convertObjectType(eruptFieldModel, list.get(1));

                        countQuery.setParameter(EruptJpaUtils.LVAL_KEY + condition.getParamKey(), begin);
                        countQuery.setParameter(EruptJpaUtils.RVAL_KEY + condition.getParamKey(), end);
                        query.setParameter(EruptJpaUtils.LVAL_KEY + condition.getParamKey(), begin);
                        query.setParameter(EruptJpaUtils.RVAL_KEY + condition.getParamKey(), end);
                        break;
                    case in:
                    case IN:
                    case nin:
                    case NIN:
                        List<?> listIn = (List<?>) condition.getValue();
                        val params = (List<Object>)condition.getValue();
                        List<Object> listIn2 =  params.stream().map(it->convertObjectType(eruptFieldModel, it))
                                .collect(Collectors.toList());
                        countQuery.setParameter(condition.getParamKey(), listIn2);
                        query.setParameter(condition.getParamKey(), listIn2);
                        break;
                    default:
                        break;
                }
            }
        }
        page.setTotal((Long) countQuery.getSingleResult());
        if (page.getTotal() > 0) {
            List list = query.setMaxResults(page.getSize())
                    .setFirstResult((page.getPage() - 1) * page.getSize()).getResultList();
            List newList = wrapMapResult(list,eruptModel);
            page.setList(newList);
        } else {
            page.setList(new ArrayList<>(0));
        }
        if (entityManager.isOpen()) {
            entityManager.close();
        }
        return page;
    }

    private EruptFieldModel findEruptFieldModel(Map<String, EruptFieldModel> eruptFieldMap, Condition condition) {
        EruptFieldModel eruptFieldModel = eruptFieldMap.get(condition.getKey());
        if(eruptFieldModel==null && condition.isComplex()){
            EruptFieldModel parent = eruptFieldMap.get(condition.getKey().split("\\.")[0]);
            EruptModel parentModel = EruptCoreService.getErupt(parent.getFieldReturnName());
            eruptFieldModel = parentModel.getEruptFieldMap().get(condition.getKey().split("\\.")[1]);
        }
        return eruptFieldModel;
    }

    public static List wrapMapResult(List list,EruptModel eruptModel) {
        List<EruptFieldModel> fieldModels = eruptModel.getToOneFields();
        if(fieldModels.size()==0 || list.size()==0){
            return list;
        }
        Object peek = list.get(0);
        if(peek instanceof Map){
            Map<String,Object> item = (Map<String, Object>) list.get(0);
            Set<String> fields = item.keySet();
            val complexFields =  fieldModels.stream().map(it->fields.stream().filter(itt->itt.startsWith(it.getFieldName()+ COMPLEX_FILED_JOINER) )
                    .collect(Collectors.toList())).collect(Collectors.toList());
            for(Object obj : list){
                convertFieldsToObj(fieldModels, (Map<String, Object>) obj, complexFields);
            }
        }
        return list;
    }

    private static void convertFieldsToObj(List<EruptFieldModel> fieldModels, Map<String, Object> item, List<List<String>> complexFields) {
        for (int i = 0; i < fieldModels.size(); i++) {
            EruptFieldModel fieldModel = fieldModels.get(i);
            String prefix = fieldModel.getFieldName()+ COMPLEX_FILED_JOINER;
            List<String> rawFields = complexFields.get(i);
            Map<String,Object> entity = new HashMap<>();
            for(String field : rawFields){
                String key = field.substring(prefix.length());
                val value = item.get(field);
                entity.put(key,value);
            }
            //Map<String,Object> entity = rawFields.stream().collect(Collectors.toMap(it->it.substring(prefix.length()), item::get));
            if(entity.size()>0){
                item.put(fieldModel.getFieldName(),entity);
            }
        }
    }


    /**
     * @param eruptModel
     * @param cols       格式：name as alias
     * @return
     */
    public List<Map<String, Object>> getDataMap(EruptModel eruptModel, List<String> cols, String sort, String... condition) {
        StringBuilder hql = new StringBuilder();
        hql.append("select new map(").append(String.join(", ", cols)).append(") from ")
                .append(eruptModel.getEruptName()).append(" as ").append(eruptModel.asTableName());
        ReflectUtil.findClassAllFields(eruptModel.getClazz(), field -> {
            if (null != field.getAnnotation(ManyToOne.class) || null != field.getAnnotation(OneToOne.class)) {
                hql.append(" left outer join ").append(eruptModel.asTableName()).append(".")
                        .append(field.getName()).append(" as ").append(field.getName());
            }
        });
        hql.append(" where 1 = 1 ");
        for (String s : condition) {
            if (StringUtils.isNotBlank(s)) {
                hql.append(EruptJpaUtils.AND).append(s);
            }
        }
        for (Filter filter : eruptModel.getErupt().filter()) {
            String filterStr = AnnotationUtil.switchFilterConditionToStr(filter);
            if (StringUtils.isNotBlank(filterStr)) {
                hql.append(EruptJpaUtils.AND).append(filterStr);
            }
        }
        hql.append(EruptJpaUtils.geneEruptHqlOrderBy(eruptModel, sort));
        EntityManager entityManager = entityManagerService.getEntityManager(eruptModel.getClazz());
        Query query = entityManager.createQuery(hql.toString());
        List list = query.getResultList();
        if (entityManager.isOpen()) {
            entityManager.close();
        }
        return list;
    }

}
